﻿using System;
using OAuthApp.Filters;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using OAuthApp.Data;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.IO.Compression;
using FluentFTP;
using System.Net;
using Microsoft.AspNetCore.Hosting;
using System.Threading;
using Swashbuckle.AspNetCore.Annotations;
using Microsoft.AspNetCore.Authorization;
using OAuthApp.Tenant;
using OAuthApp.Services;
using System.Collections.Generic;
using OAuthApp.ApiModels.AppsController;
using System.Text;
using System.Net.Http;

namespace OAuthApp.Apis
{
    [SwaggerTag("应用")]
    [ServiceFilter(typeof(ApiRequestLoggingAttribute))]
    public class AppsController : BaseController
    {
        private readonly IWebHostEnvironment _env;
        private readonly AppDbContext _context;
        private readonly UploadService _uploader;

        public AppsController(AppDbContext context,
            TenantDbContext tenantDbContext,
            IWebHostEnvironment env,
            IHttpContextAccessor contextAccessor,
            UploadService uploader)
        {
            _context = context;
            _tenantContext = tenantDbContext;
            _env = env;
            _tenant = contextAccessor.HttpContext.GetTenantContext();
            _uploader = uploader;
        }

        [HttpGet("Market")]
        [SwaggerOperation(OperationId = "AppMarket")]
        [EncryptResultFilter]
        [AllowAnonymous]
        public IActionResult Market(string tag,int skip, int take)
        {
            var q = _context.Apps.Where(x => x.Share && !x.IsDelete).AsQueryable();

            if (!string.IsNullOrWhiteSpace(tag))
            {
                q = q.Where(x => x.Tags.Contains(tag));
            }

            var total = q.Count();

            var data = q.Skip(skip).Take(take).OrderByDescending(x => x.ID).ToList();

            return OK(new
            {
                total,
                data
            });
        }

        [HttpGet]
        [SwaggerOperation(OperationId = "Apps")]
        [EncryptResultFilter]
        public IActionResult List(long projectId, int skip, int take)
        {
            var _teamProjects = projectIDs();
            
            var IsTeamMember = false;

            if (_teamProjects.Contains(projectId))
            {
                IsTeamMember = true;
            }

            var q = _context.Apps
                .Where(x => !x.IsDelete && x.ProjectID == projectId
                && (x.UserID == UserID || IsTeamMember)).AsQueryable();

            var total = q.Count();

            var data = q.Skip(skip).Take(take).OrderByDescending(x => x.ID).ToList();

            return OK(new
            {
                total,
                data
            });
        }

        [HttpGet("{id}")]
        [SwaggerOperation(OperationId = "App")]
        [EncryptResultFilter]
        public IActionResult Get(long id)
        {
            var _teamProjects = projectIDs();

            var result = _context.Apps
               .FirstOrDefault(x => x.ID == id && 
               (x.UserID == UserID || _teamProjects.Contains(x.ProjectID)));

            if (result == null)
            {
                return NotFound();
            }

            return OK(result);
        }

        [HttpPut("{id}")]
        [SwaggerOperation(OperationId = "AppPut")]
        public IActionResult Put(long id, App app)
        {
            var _teamProjects = projectIDs();

            if (id != app.ID || !_context.Apps.Any(x => x.ID == id && 
            (x.UserID == UserID || _teamProjects.Contains(x.ProjectID))))
            {
                return NotFound();
            }

            // 允许多个应用使用同一个路径，因为可以发布到不同的服务器
            //if (_context.Apps.Any(x => x.ID != id && x.ServerPath.Equals(app.ServerPath)))
            //{
            //    return Error("已存在的站点路径");
            //}

            app.UserID = UserID;

            _context.Entry(app).State = EntityState.Modified;

            try
            {
                _context.SaveChanges();
            }
            catch (Exception ex)
            {
                return Error(ex.Message);
            }

            return OK(true);
        }

        [HttpPost]
        [SwaggerOperation(OperationId = "AppPost")]
        public IActionResult Post(App app)
        {
            // 允许多个应用使用同一个路径，因为可以发布到不同的服务器
            //if(_context.Apps.Any(x=>x.ServerPath.Equals(app.ServerPath)))
            //{
            //    return Error("已存在的站点路径");
            //}

            app.UserID = UserID;

            _context.Apps.Add(app);

            _context.SaveChanges();

            return OK(new { id = app.ID, serverPath = app.ServerPath });
        }

        [HttpDelete("{id}")]
        [SwaggerOperation(OperationId = "AppDelete")]
        public IActionResult Delete(long id)
        {
            var _teamProjects = projectIDs();

            var result = _context.Apps
                .FirstOrDefault(x => x.ID == id &&
                (x.UserID == UserID || _teamProjects.Contains(x.ProjectID)));

            if (result == null)
            {
                return NotFound();
            }

            _context.Execute("DELETE FROM AppChatMessages WHERE AppID = " + id);
            _context.Execute("DELETE FROM AppRanks WHERE AppID = " + id);
            _context.Execute("DELETE FROM AppUsers WHERE AppID = " + id);
            _context.Execute("DELETE FROM AppVersions WHERE AppID = " + id);

            var HasStorageTable = _context.QueryFirstOrDefault<int>
                ($"SELECT count(1) AS Total FROM sqlite_master WHERE type=\"table\" AND name = \"_AppStorage_${id}\"");

            if (HasStorageTable > 0)
            {
                _context.Execute("DROP TABLE _AppStorage_" + id);
            }

            _context.Apps.Remove(result);
            _context.SaveChanges();

            return OK(true);
        }

        [HttpGet("{id}/Transfer")]
        [SwaggerOperation(OperationId = "AppTransfer")]
        [EncryptResultFilter]
        public IActionResult Transfer(long id,long projectId)
        {
            var result = _context.Apps.FirstOrDefault(x => x.ID == id &&x.UserID == UserID);

            if (result == null)
            {
                return NotFound();
            }

            result.ProjectID = projectId;

            _context.SaveChanges();

            return OK(result);
        }

        [HttpPost("Release")]
        [SwaggerOperation(OperationId = "AppRelease")]
        public async Task<IActionResult> Release(
            [Required][FromQuery] long id,
            [Required][FromQuery] long serverID,
            [Required][FromQuery] bool rollback,
            [FromQuery] bool backup,
            [FromQuery] string tag,
            [FromForm] string desc,
            [FromForm] IFormFile file)
        {
            var app = _context.Apps.Where(x => x.ID.Equals(id) && x.IsDelete == false)
                .FirstOrDefault();

            if (app == null)
            {
                return Error("应用不存在");
            }

            if (string.IsNullOrWhiteSpace(app.ServerPath))
            {
                return Error("未设置服务器目录");
            }

            var server = _tenantContext.TenantServers.FirstOrDefault(x => x.ID.Equals(serverID));

            if (server == null)
            {
                return Error("发布服务器不存在");
            }

            if (file.Length < 0 || file.Length > 20 * 1024 * 1024)
            {
                return Error("程序应小于10MB");
            }

            var fileName = Guid.NewGuid().ToString("n");

            var destinationPath = Path.Combine(_env.WebRootPath,
                "_temp",
                id.ToString(),
                DateTime.Now.ToString("yyyyMMdd"),
                fileName);

            byte[] fileData = null;

            using (var ms = new MemoryStream())
            {
                await file.CopyToAsync(ms, CancellationToken.None);
                fileData = ms.ToArray();
            }

            using (var fs = file.OpenReadStream())
            {
                using var zip = new ZipArchive(fs, ZipArchiveMode.Read, true);

                zip.ExtractToDirectory(destinationPath);
            }

            var directoryPath = string.Format(Path.Combine(server.RootFolder, app.ServerPath), _tenant.Id);

            var appPublishMD5Mark = Path.Combine(directoryPath, app.AppKey) + ".txt";

            var samePathAppKeys = _context.Apps.Where(x => x.ServerPath.Equals(app.ServerPath))
                .Select(x => x.AppKey)
                .ToList();

            #region ftp发布
            using (var client = new FtpClient(server.ServerUrl))
            {
                client.Credentials = new NetworkCredential(server.UserName, server.Password);
                
                client.Connect();

                #region 相同目录，多个应用处理
                if (samePathAppKeys.Count > 1)
                {
                    // 当前目录没有发布标记
                    if (!client.FileExists(appPublishMD5Mark))
                    {
                        var allreadyUsed = false;

                        // 检测该目录是否有发布标记
                        for (var i = 0; i < samePathAppKeys.Count; i++)
                        {
                            if (samePathAppKeys[i] == app.AppKey) { continue; }

                            var _MD5Mark = Path.Combine(directoryPath, samePathAppKeys[i]) + ".txt";

                            allreadyUsed = client.FileExists(_MD5Mark);

                            if (allreadyUsed)
                            {
                                break;
                            }
                        }

                        if (allreadyUsed)
                        {
                            return Error("目录已被其他应用使用");
                        }
                    }
                }
                #endregion

                client.UploadDirectory(destinationPath,
                    directoryPath,
                    FtpFolderSyncMode.Update,
                    FtpRemoteExists.Overwrite);

                client.UploadBytes(Encoding.UTF8.GetBytes(DateTime.Now.Ticks.ToString()),
                    Path.Combine(directoryPath, app.AppKey + ".txt"));

                client.Disconnect();
            }
            #endregion

            if (!rollback && backup)
            {
                var version = DateTime.Now.ToString("yyyyMMddHHmmss");

                var savePath = $"{_tenant.Id}/{ChannelCodes.AppVersion}/{id}/{version}_{file.FileName}";

                _uploader.Upload(savePath, file);

                _context.AppVersions.Add(new AppVersion()
                {
                    AppID = id,
                    AppServerID = server.ID,
                    PackageBackupUri = AppConst.BlobServer + "/" + savePath,
                    Ver = version,
                    UserID = UserID,
                    Tag = tag,
                    Description = desc
                });

                _context.SaveChanges();

                #region 累计版本备份用量
                _tenantContext.TenantOrders.Add(new TenantOrder()
                {
                    Amount = fileData.Length,
                    ChannelAppID = id.ToString(),
                    ChannelCode = ChannelCodes.AppVersion,
                    TenantID = _tenant.Id
                });
                _tenantContext.SaveChanges();
                #endregion
            }

            #region 累计站点空间用量
            _tenantContext.TenantOrders.Add(new TenantOrder()
            {
                Amount = fileData.Length,
                ChannelAppID = id.ToString(),
                ChannelCode = ChannelCodes.App,
                TenantID = _tenant.Id
            });
            _tenantContext.SaveChanges();
            #endregion

            if (server.RootFolder.Equals("/{0}/"))
            {
                return OK(server.WebSiteUrl + "/" + _tenant.Id + "/" + app.ServerPath);
            }
            else
            {
                return OK(server.WebSiteUrl + "/" + app.ServerPath);
            }
        }

        List<long> projectIDs()
        {
            return _context.Teams.Where(x =>
           x.ChannelCode == ChannelCodes.Project &&
           x.UserID == UserID)
               .Select(x => long.Parse(x.ChannelAppID)).ToList();
        }

        // JS 客户端
        [HttpGet("{appId}/Info")]
        [SwaggerOperation(OperationId = "AppInfo")]
        [AllowAnonymous]
        public IActionResult Info(long appId, [FromQuery] string propCode = PropertyTag.Client, [FromQuery] string blobPath = "")
        {
            var info = _context.Apps.Find(appId);

            var props = _context.PropertySettings
                .Where(x => x.ChannelCode == ChannelCodes.App &&
                x.ChannelAppId == appId &&
                x.APICanUse == true &&
                x.Tag == propCode)
                .Select(x => new { code = x.Name, value = x.Value })
                .ToList();

            #region 文件存储
            var blobs = new List<string>();
            var blobRoot_path = Path.Combine(AppConst.BlobRoot, _tenant.Id.ToString(),
                   ChannelCodes.App, appId.ToString());
            if (!string.IsNullOrWhiteSpace(blobPath) && blobPath.Length > 1)
            {
                blobPath = blobPath.Substring(1);

                blobRoot_path = Path.Combine(blobRoot_path, blobPath);
            }
            if (Directory.Exists(blobRoot_path))
            {
                var blob_dir = new DirectoryInfo(blobRoot_path);

                //blob_directories = blob_dir.GetDirectories().Select(x => new AppInfoBlobItem()
                //{
                //    Name = x.Name,
                //    CreateDate = x.CreationTime,
                //    LastUpdate = x.LastWriteTime
                //}).ToList();

                blobs = blob_dir.GetFiles().Select(x =>
                $"{AppConst.BlobServer}/{_tenant.Id.ToString()}/{ChannelCodes.App}/{appId.ToString()}/{blobPath}{x.Name}").ToList();
            }
            #endregion

            return OK(new
            {
                info,
                props,
                blobs
            });
        }

        /// <summary>
        /// 服务器文件列表
        /// </summary>
        [HttpGet("{id}/Files")]
        [SwaggerOperation(OperationId = "AppFiles")]
        [EncryptResultFilter]
        public IActionResult Files(long id,long serverId)
        {
            var server = _tenantContext.TenantServers
                .Where(x => x.ID == serverId).FirstOrDefault();

            if (server == null)
            {
                return Error("不存在的服务器");
            }

            var appPath = _context.Apps.Where(x => x.ID == id)
                .Select(x => x.ServerPath).FirstOrDefault();

            if(string.IsNullOrWhiteSpace(appPath))
            {
                return Error("不存在的应用");
            }

            var result = GetList(server.ServerUrl, server.UserName,
                server.Password,
                 string.Format(server.RootFolder,_tenant.Id),
                appPath);

            return OK(result);
        }

        private AppFileItem GetList(string ftp_url, string ftp_userName, string ftp_password, string rootPath, string subPath)
        {
            var startPath = $"{rootPath}{subPath}";

            var root = new AppFileItem(startPath.GetHashCode(), subPath, startPath, "directory")
            {
                children = new List<AppFileItem>()
            };

            using (var conn = new FtpClient(ftp_url, ftp_userName, ftp_password))
            {
                conn.Connect();

                foreach (var item in conn.GetListing(startPath))
                {
                    if (item.Type == FtpObjectType.Directory)
                    {
                        var directoryItem = new AppFileItem(item.GetHashCode(), item.Name, item.FullName, "directory")
                        {
                            children = new List<AppFileItem>()
                        };

                        root.children.Add(directoryItem);

                        _GetList(conn, item.FullName, directoryItem);
                    }

                    else if (item.Type == FtpObjectType.File)
                    {
                        root.children.Add(new AppFileItem(item.GetHashCode(), item.Name, item.FullName, Path.GetExtension(item.Name))
                        {
                            children = new List<AppFileItem>()
                        });
                    }
                }
            }

            return root;
        }

        private void _GetList(FtpClient conn, string path, AppFileItem node)
        {
            foreach (var item in conn.GetListing(path))
            {
                if (item.Type == FtpObjectType.Directory)
                {
                    var directoryItem = new AppFileItem(item.GetHashCode(), item.Name, item.FullName, "directory")
                    {
                        children = new List<AppFileItem>()
                    };

                    node.children.Add(directoryItem);

                    _GetList(conn, item.FullName, directoryItem);
                }

                else if (item.Type == FtpObjectType.File)
                {
                    node.children.Add(new AppFileItem(item.GetHashCode(), item.Name, item.FullName, Path.GetExtension(item.Name))
                    {
                        children = new List<AppFileItem>()
                    });
                }
            }
        }

        [HttpPost("{id}/File")]
        [SwaggerOperation(OperationId = "AppUploadFile")]
        public IActionResult UploadFile(long id,[FromBody] UploadFileRequest value)
        {
            var app = _context.Apps.Where(x => x.ID.Equals(id) && x.IsDelete == false)
                .FirstOrDefault();

            if (app == null)
            {
                return Error("应用不存在");
            }

            if (string.IsNullOrWhiteSpace(app.ServerPath))
            {
                return Error("未设置服务器目录");
            }

            var server = _tenantContext.TenantServers.FirstOrDefault(x => x.ID.Equals(value.serverID));

            if (server == null)
            {
                return Error("发布服务器不存在");
            }

            if (value.content.Length < 0 || value.content.Length > 10 * 1024 * 1024)
            {
                return Error("文件内容应小于10MB");
            }

            var fileData = Encoding.UTF8.GetBytes(value.content);

            #region ftp发布
            using (var client = new FtpClient(server.ServerUrl))
            {
                client.Credentials = new NetworkCredential(server.UserName, server.Password);

                client.Connect();

                client.UploadBytes(fileData, value.path, FtpRemoteExists.Overwrite,true);

                client.Disconnect();
            }
            #endregion

            #region 累计站点空间用量
            _tenantContext.TenantOrders.Add(new TenantOrder()
            {
                Amount = fileData.Length,
                ChannelAppID = id.ToString(),
                ChannelCode = ChannelCodes.App,
                TenantID = _tenant.Id
            });
            _tenantContext.SaveChanges();
            #endregion

            var result = Path.Combine($"{server.WebSiteUrl}{value.path}");

            return OK(result);
        }

        [HttpDelete("{id}/File")]
        [SwaggerOperation(OperationId = "AppDeleteFile")]
        public IActionResult DeleteFile(long id, long serverID, string path)
        {
            var app = _context.Apps.Where(x => x.ID.Equals(id) && x.IsDelete == false)
                .FirstOrDefault();

            if (app == null)
            {
                return Error("应用不存在");
            }

            if (string.IsNullOrWhiteSpace(app.ServerPath))
            {
                return Error("未设置服务器目录");
            }

            var server = _tenantContext.TenantServers.FirstOrDefault(x => x.ID.Equals(serverID));

            if (server == null)
            {
                return Error("发布服务器不存在");
            }

            long fileSize = 0;

            #region ftp
            using (var client = new FtpClient(server.ServerUrl))
            {
                client.Credentials = new NetworkCredential(server.UserName, server.Password);

                client.Connect();

                if (client.FileExists(path))
                {
                    fileSize = client.GetFileSize(path);

                    client.DeleteFile(path);
                }
                else
                {
                    return Error("文件不存在");
                }

                client.Disconnect();
            }
            #endregion

            #region 累计站点空间用量
            _tenantContext.TenantOrders.Add(new TenantOrder()
            {
                Amount = -fileSize,
                ChannelAppID = id.ToString(),
                ChannelCode = ChannelCodes.App,
                TenantID = _tenant.Id
            });
            _tenantContext.SaveChanges();
            #endregion

            return OK(true);
        }

        [HttpDelete("{id}/Directory")]
        [SwaggerOperation(OperationId = "AppDeleteDirectory")]
        public IActionResult DeleteDirectory(long id, long serverID, string path)
        {
            var app = _context.Apps.Where(x => x.ID.Equals(id) && x.IsDelete == false)
                .FirstOrDefault();

            if (app == null)
            {
                return Error("应用不存在");
            }

            if (string.IsNullOrWhiteSpace(app.ServerPath))
            {
                return Error("未设置服务器目录");
            }

            var server = _tenantContext.TenantServers.FirstOrDefault(x => x.ID.Equals(serverID));

            if (server == null)
            {
                return Error("发布服务器不存在");
            }

            long fileSize = 0;

            #region ftp
            using (var client = new FtpClient(server.ServerUrl))
            {
                client.Credentials = new NetworkCredential(server.UserName, server.Password);

                client.Connect();

                if (client.DirectoryExists(path))
                {
                    var lists = client.GetListing(path);

                    var ftpItem = lists.FirstOrDefault();

                    if (ftpItem != null)
                    {
                        fileSize = ftpItem.Size;
                    }

                    client.DeleteDirectory(path);
                }
                else
                {
                    return Error("文件夹不存在");
                }

                client.Disconnect();
            }
            #endregion

            #region 累计站点空间用量
            if (fileSize > 0)
            {
                _tenantContext.TenantOrders.Add(new TenantOrder()
                {
                    Amount = -fileSize,
                    ChannelAppID = id.ToString(),
                    ChannelCode = ChannelCodes.App,
                    TenantID = _tenant.Id
                });
                _tenantContext.SaveChanges();
            }
            #endregion

            return OK(true);
        }

        [HttpPost("HttpProxy")]
        [SwaggerOperation(OperationId = "AppHttpProxy")]
        [AllowAnonymous]
        public string HttpProxy([FromBody] WebProxyRequest value)
        {
            using (var hc = new HttpClient())
            {
                var hrm = new HttpRequestMessage(new HttpMethod(value.method), value.uri);

                if (!string.IsNullOrWhiteSpace(value.jsonData))
                {
                    hrm.Content = new StringContent(value.jsonData, Encoding.UTF8, "application/json");
                }

                if (value.headerNames != null && value.headerValues != null &&
                    value.headerNames.Count == value.headerValues.Count)
                {
                    for (var i = 0; i < value.headerNames.Count; i++)
                    {
                        hc.DefaultRequestHeaders.TryAddWithoutValidation(value.headerNames[i], value.headerValues[i]);
                    }
                }

                var result = hc.SendAsync(hrm).Result;

                if (result.IsSuccessStatusCode)
                {
                    return result.Content.ReadAsStringAsync().Result;
                }
            }

            return string.Empty;
        }


        [HttpPost("{id}/Directory")]
        [SwaggerOperation(OperationId = "AppCreateDirectory")]
        public IActionResult CreateDirectory(long id, [FromBody] CreateDirectoryRequest value)
        {
            var app = _context.Apps.Where(x => x.ID.Equals(id) && x.IsDelete == false)
                .FirstOrDefault();

            if (app == null)
            {
                return Error("应用不存在");
            }

            if (string.IsNullOrWhiteSpace(app.ServerPath))
            {
                return Error("未设置服务器目录");
            }

            var server = _tenantContext.TenantServers.FirstOrDefault(x => x.ID.Equals(value.serverID));

            if (server == null)
            {
                return Error("发布服务器不存在");
            }

            #region ftp
            using (var client = new FtpClient(server.ServerUrl))
            {
                client.Credentials = new NetworkCredential(server.UserName, server.Password);

                client.Connect();

                if (!client.DirectoryExists(value.path))
                {
                    client.CreateDirectory(value.path);
                }
                else
                {
                    return Error("文件夹已存在");
                }

                client.Disconnect();
            }
            #endregion

            return OK(true);
        }

        /// <summary>
        /// 日志统计
        /// </summary>
        [HttpGet("{appKey}/LogReport")]
        [SwaggerOperation(OperationId = "AppLogReport")]
        [EncryptResultFilter]
        public IActionResult LogReport(string appKey, int timeRange = 0)
        {
            var QueryBuilder = new List<string>();

            QueryBuilder.Add("SELECT T.OperationId as API, ");
            QueryBuilder.Add("(SELECT json_group_array(DISTINCT(x.reqPath)) FROM AppLogs X WHERE X.OperationId = T.OperationId) as Items,");
            QueryBuilder.Add("COUNT(DISTINCT T.UserID) Users, ");
            QueryBuilder.Add("COUNT(1) AS Total  ");
            QueryBuilder.Add("FROM AppLogs T ");

            QueryBuilder.Add("WHERE T.AppKey = @appKey");

            if (timeRange == 1)
            {
                QueryBuilder.Add(" AND T.ReqEnd >= datetime('now', 'start of day', '+0 day') AND T.ReqEnd < datetime('now', 'start of day', '+1 day')");
            }
            else if (timeRange == 2)
            {
                QueryBuilder.Add(" AND T.ReqEnd >= datetime('now','start of day',' - 6 day','weekday 1') AND T.ReqEnd < datetime(datetime('now','start of day','weekday 0'),'start of day',' + 1 day')");
            }
            else if (timeRange == 3)
            {
                QueryBuilder.Add(" AND T.ReqEnd >= datetime('now','start of month','+0 month','-0 day') AND T.ReqEnd < datetime('now','start of month','+1 month','0 day')");
            }

            QueryBuilder.Add("GROUP BY T.OperationId");

            var cmd = string.Join(" ", QueryBuilder);

            var result = _context.Query<LogReportResponse>(cmd, new
            {
                appKey
            });

            return OK(result);
        }

        /// <summary>
        /// 日志
        /// </summary>
        [HttpGet("{appKey}/Logs")]
        [SwaggerOperation(OperationId = "AppLogs")]
        [EncryptResultFilter]
        public IActionResult Logs(string appKey,
            string reqPath,
            string userId,
            string sId,
            int skip,
            int take)
        {
            var q = _context.AppLogs
                .Where(x => x.AppKey == appKey);

            if (!string.IsNullOrWhiteSpace(reqPath))
            {
                q = q.Where(x => x.ReqPath.ToLower().IndexOf(reqPath) > -1);
            }

            if (!string.IsNullOrWhiteSpace(userId))
            {
                q = q.Where(x => x.UserID == userId);
            }

            if (!string.IsNullOrWhiteSpace(sId))
            {
                q = q.Where(x => x.UserGroup == sId);
            }

            var total = q.Count();

            var data = q.OrderByDescending(x => x.ID).Skip(skip).Take(take).ToList();

            return OK(new
            {
                total,
                data
            });
        }
    }
}
